#include "ws.client.h"
#include "ws.request.h"
#include "ws.packer.h"
#include "ws.unpacker.h"
#include "ws.rpc.h"

#ifdef DEBUG
#include <iostream>
#endif

TOB::WS::Client::Client(TOB::Nameserver::IP nsip, std::string username, std::string password) : AuthRequired(nsip, username, password) {
#ifdef DEBUG
  std::cout << "Created client: " << this << std::endl;
#endif
}

Code::Type TOB::WS::Client::request(std::string request, Data& data) {
  // parse request
  TOB::WS::Request* req;
  try {
    req = new TOB::WS::Request(request);
  } catch (RequestException) {
    throw TOB::WS::ClientException("Parse error", Code::WS_ParseError);
    return Code::WS_ParseError;
  }
  
  // create data for send
  TOB::WS::Packer* packer = NULL;
  try {
    packer = new TOB::WS::Packer(request, &data, nsip, username, password);
  } catch (PackerException) {
    throw TOB::WS::ClientException("Unknown scheme", Code::WS_UnknownScheme);
    return Code::WS_UnknownScheme;
  }
  
  // connect to server
  CLIENT* clnt = clnt_create(req->getHost().c_str(), WS_PROG, WS_VERSION, "udp");
  if (clnt == NULL) {
    delete req;
    delete packer;
    throw TOB::WS::ClientException("Connect error", Code::WS_ConnectError);
    return Code::WS_ConnectError;
  }
  
  // sending
  int* respond;
  respond = start_sending_1(NULL, clnt);
  if (respond == NULL) {
    //if (clnt != NULL) { clnt_destroy(clnt); }
    delete req;
    delete packer;
    throw TOB::WS::ClientException("Connect error", Code::WS_ConnectError);
    return Code::WS_ConnectError;
  }
  int token = *respond;
  if (token == -1) {
    //if (clnt != NULL) { clnt_destroy(clnt); }
    delete req;
    delete packer;
    throw TOB::WS::ClientException("Send failure", Code::WS_SendFailure);
    return Code::WS_SendFailure;
  }
  for (int i = 0; i < packer->getCount(); ++i) {
    (*packer)[i].token = token;
    
    respond = send_1(&((*packer)[i]), clnt);
    if (respond == NULL) {
      //if (clnt != NULL) { clnt_destroy(clnt); }
      delete req;
      delete packer;
      throw TOB::WS::ClientException("Connect error", Code::WS_ConnectError);
      return Code::WS_ConnectError;
    }
    
    int result = *respond;
    if (result == -1) {
      //if (clnt != NULL) { clnt_destroy(clnt); }
      delete req;
      delete packer;
      throw TOB::WS::ClientException("Send failure", Code::WS_SendFailure);
      return Code::WS_SendFailure;
    }
  }
  
  respond = end_sending_1(&token, clnt);
  if (respond == NULL) {
    //if (clnt != NULL) { clnt_destroy(clnt); }
    delete req;
    delete packer;
    throw TOB::WS::ClientException("Connect error", Code::WS_ConnectError);
    return Code::WS_ConnectError;
  }
  int endSending = *respond;
  if (endSending == -1) {
    //if (clnt != NULL) { clnt_destroy(clnt); }
    delete req;
    delete packer;
    throw TOB::WS::ClientException("Assembly failure", Code::WS_AssemblyFailure);
    return Code::WS_AssemblyFailure;
  }
  
  // get result
  TOB::WS::Packer resive = TOB::WS::Packer();
  package* ppack;
  int getResult;
  while (true) {
    respond = ready_pack_1(&token, clnt);
    if (respond == NULL) {
      //if (clnt != NULL) { clnt_destroy(clnt); }
      delete req;
      delete packer;
      throw TOB::WS::ClientException("Connect error", Code::WS_ConnectError);
      return Code::WS_ConnectError;
    }
    
    getResult = *respond;
    if (getResult <= 0) break;
    
    ppack = resive_1(&token, clnt);
    if (ppack == NULL) {
      //if (clnt != NULL) { clnt_destroy(clnt); }
      delete req;
      delete packer;
      throw TOB::WS::ClientException("Resive failure", Code::WS_ResiveFailure);
      return Code::WS_ResiveFailure;
    }
    resive.push(*(ppack));
  }
  if (getResult == -1) {
    //if (clnt != NULL) { clnt_destroy(clnt); }
    delete req;
    delete packer;
    throw TOB::WS::ClientException("Resive failure", Code::WS_ResiveFailure);
    return Code::WS_ResiveFailure;
  }
  
  // unpack
  TOB::WS::Unpacker* unpacker = NULL;
  try {
    unpacker = new TOB::WS::Unpacker(resive);
  } catch (UnpackerException) {
    //if (clnt != NULL) { clnt_destroy(clnt); }
    delete req;
    delete packer;
    throw TOB::WS::ClientException("Assembly failure", Code::WS_AssemblyFailure);
    return Code::WS_AssemblyFailure;
  }
  data = unpacker->getData();
  
  // disconnect
  if (clnt != NULL) { clnt_destroy(clnt); }
  // free memory
  delete req;
  delete packer;
  delete unpacker;
  
  return Code::Okay;
}

TOB::WS::Client::~Client() {
#ifdef DEBUG
  std::cout << "Deleted client: " << this << std::endl;
#endif
}

TOB::WS::ClientException::ClientException(std::string message, Code::Type errorCode) {
  this->message = "WS-Client: " + message;
  this->errorCode = errorCode;
}

Code::Type TOB::WS::ClientException::getErrorCode() {
  return errorCode;
}

std::string TOB::WS::ClientException::getMessage() {
  return message;
}

void TOB::WS::ClientException::writeErrorToLog() {
  TOB::Log::error(message);
}
